函数的执行过程、递归函数、匿名函数

装饰器, 装饰器原理, 装饰器的好处, 装饰器带来的副作用及解决方法, 带参装饰器及应用

装饰器

  • 装饰器是可调用的对象, 其参数是另一个函数(被装饰的函数) 。 装饰器可能会处理被装饰的函数, 然后把它返回, 或者将其替换成另一个函数或可调用对象
  • 它经常用于有切面需求的场景:如 插入日志、性能测试、事物处理、缓存、权限校验等等

装饰器的推导式1

  • 需求
    一个加法函数,想要增强它的功能,能够输出被调用过以及调用的参数信息
1
2
3
4
5
6
7
8
9
def add(x,y):
return x + y

# 增加信息输出功能
def add(x,y):
print(x,y)
return x + y

add(4,5)

上面的加法函数是完成了需求,但是有以下缺点

  • 打印语句的耦合度太高,如果有100个函数需要同样的功能,则这100个函数都需要添加相同的代码
  • 加法函数属于业务功能,而输出信息的功能,属于非业务功能代码,不该放在业务函数加法当中

为了做到业务功能分离,我们可以利用高阶函数进行将功能函数与业务函数进行分类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def add(x,y):
return x + y

def logger(fn):
print('Call function begin') # 增强的输出
ret = fn(10,5)
print('Call function end') # 增强的功能
return ret

print(logger(add))

# 此时将函数add作为logger的参数传入,标准的高阶函数,此时相当于 以下函数

def logger(add):
print('Call function begin')
ret = add(10,5)
print('Call function end')
return ret

print(logger(add))

遗留问题
以上功能函数也就是业务函数已经实现分离,但是问题在于参数的传入,此时函数的参数是在函数体内固定死了,但是现实中的函数参数是灵活的,不可能一成不变的,因此需要对参数进行选择性传入

装饰器的推导式2 自由参数传入

  • 为了解决自由参数的传入
  • 在嵌套函数中,外层函数的形参对于外层函数就是本质的形参,但是对于内层函数就是实参。因此外层函数传入实参时,经过外层 args, ** kwargs贪婪模式吸收,形成tuple和dict的形式,传递给内层函数,内层函数拿到就是tuple和dict,再经过实参出入时解构 args,** kwargs。最后就将外层函数传入的实参梯队传入内层函数。
1
2
3
4
5
6
7
8
9
# 解决只有参数的传入,原本函数如下:
def logger(fn):
print('Call function begin')
ret = fn(10,30)
print('Call function end')
return ret

def add(x,y):
return x + y
1
2
3
4
5
6
7
8
9
10
11
12
# 为了解决只有参数的传入
def logger(fn,*args,**kwargs):

print('Call function begin ')
func = fn(*args,**kwargs)
print('Call function end')
return func

def add(x,y):
return x + y

logger(add,5,6)
Call function begin 
Call function end





11

装饰器的推导式3 柯里化

  • 为了将fn(x,y)变形为fn(x)(y)语法糖格式的需求,将函数进行柯里化
  • 为了将logger(add,4,5)变形为logger(add)(5,6)
  • 用到了闭包的概念
  • 依据柯里化变形三步骤
    1. 函数中第一个或某一个参数提升至外层函数,并对外层函数重命名
    2. 原本命名的函数参数抛出提升,其余的变形为新命名函数的函数体
    3. 新命名函数返回值为原本之前的函数
1
2
3
4
5
6
7
8
9
10
11
12
13
# 新函数的柯里化变形
def logger(fn):
def _logger(*args,**kwargs):
print('Call function begin')
ret = fn(*args,**kwargs)
print('Call function end')
return ret
return _logger

def add(x,y):
return x + y

logger(add)(4,5)
Call function begin
Call function end





9

装饰器的推导式4 语法糖

  • 上述变形已经实现柯里化调用,但是每次需要通过增强功能函数调用
  • 为了解决通过功能函数调用业务函数的问题,直接采用语法糖,抛弃功能函数调用业务函数,直接采用功能函数调用,这会产生对外功能函数没发生什么变化的错觉,但是功能却已经增强了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def logger(fn):
def _logger(*args,**kwargs):
print('Call function begin')
ret = fn(*args,**kwargs)
print('Call function end')

return ret
return _logger

@logger # add = logger(add) # 通过语法糖实现增强功能
def add(x,y):
return x + y
# 语法糖 @logger === > add == logger(add),因此此时add == _logger

add(4,5)
Call function begin
Call function end





9

为什么要进行柯里化
柯里化就是为了配合语法糖,语法糖相当于将语法糖下面的函数作为实参传递给语法糖函数
原函数去哪里了

1
2
3
4
5
@logger   # add = logger(add)    # 通过语法糖实现增强功能
def add(x,y):
return x + y
# 语法糖 @logger === > add == logger(add),因此此时add == _logger
# add(*args,**kwargs) == _logger(*args,**kwargs)

既然add已经不再是原来的 add 函数,那么增强功能函数要怎么调用 x+y 呢,此时就要用到了闭包的作用了

  • 首先: 将函数add作为实参传递给语法糖函数,这是语法糖函数就产生了闭包,内存函数fn就已经记录下add函数了
  • 其次: 将语法糖返回的函数覆盖现有的add函数
  • 最后: add函数就等价于 _logger函数了, 可以通过调用 _ name 或者 _ doc _ 属性进行验证

装饰器的分类

无参装饰器

根据如上推导公式即可得到无参装饰器

1
2
3
4
5
6
7
8
9
10
11
12
13
def logger(fn):
def _logger(*args,**kwargs):
print('Call function begin')
ret = fn(*args,**kwargs)
print('Call function end')
return ret
return _logger

@logger # add = logger(add)
def add(x,y):
return x + y

add(4,5)
Call function begin
Call function end





9

带参装饰器

  • 带参装饰器它是一个函数
  • 函数作为它的参数
  • 返回值是一个不带参的装饰器函数
  • 可以看做是在装饰器外层又加上一层函数,三层嵌套函数
  • 带参装饰器的参数可以有多个,使用闭包功能进行传参
  • 使用 @functionname(参数列表)的方式调用,等价于add = logger(参数列表)(add)
  • 格式如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import datetime
def logger(duration):
def _logger(fn):
def wapper(*args,**kwargs):
print('Call logger begin')
start = datetime.datetime.now()
ret = fn(*args,**kwargs)
delta = (datetime.datetime.now()-start).total_seconds()
print('Call logger end')
print('so slow') if duration < delta else print('so fast')

return ret
return wapper
return _logger

@logger(5) # add = logger(5)(add)
def add(x,y):
return x + y

add(4,5)
Call logger begin
Call logger end
so fast





9

返回值为装饰函数

  • 根据带参装饰器和不带参装饰器可知,经过装饰后的函数已经不再是原本的函数了,如以上的add函数已经变成_logger函数,因此想要知道函数的具体内容只能通过查看函数的 add 属性和 doc 属性可知,原本的函数已经被装饰器属性覆盖了,是因为功能函数已经传递给装饰器函数了,但是属性尚未传递
  • 解决思路; 可以通过传递功能函数的同时,传递属性,此时外界看来一切都没有变化,只是简单的装饰过而已,无法看到装饰器内部复杂的变化
  • 解决方法1:在装饰器中,手动将装饰器得到的属性修改为要装饰的属性
1
2
wrapper.__name__ = wrapped.__name__
wrapper.__doc__ = wrapped.__doc__
  • 在装饰器中,调用修改属性函数,外界定义修改属性函数
1
2
3
def property(wapped,wapper):
wrapper.__name__ = wrapped.__name__
wrapper.__doc__ = wrapped.__doc__
  • 通过装饰器进行修改属性,但是装饰器返回的不是另一个函数,返回的就是自己,由修改属性函数柯里化,返回装饰器函数即可
1
2
3
4
5
6
def propety(wapped):
def _propety(wapper):
wrapper.__name__ = wrapped.__name__
wrapper.__doc__ = wrapped.__doc__
return wapper # 内层函数返回的一定是要返回 参数传入的装饰器函数
return propety

举例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import datetime
import time

def propety(src):

def _propety(dest):
print(dest.__name__,)
dest.__name__ = src.__name__
dest.__doc__ = src.__doc__

return dest
return _propety


def logger(fn):

@propety(fn) # _logger = prorety(fn)(_logger)
def _logger(*args,**kwargs):

print('Call logger begin')

start = datetime.datetime.now()
ret = fn(*args,**kwargs)
delta = (datetime.datetime.now() - start).total_seconds()

print(delta)

print('Call logger end')

return ret

return _logger


@logger # add = logget(add)
def add(x,y):
"""This is add function"""
time.sleep(1)
return x + y

print('{} {} {}'.format(add(4,5),add.__name__,add.__doc__))
_logger
Call logger begin
1.000784
Call logger end
9  add  This is add function

装饰器的副作用

有上述的案例可知,要被装饰的函数在给装饰器通过闭包传递功能时,尚未把属性传递,使得我们看到的add函数属性是_logger的属性,而不是add的属性。

  • 解决方式1:函数思路解决问题,定义一个拷贝函数属性的函数
    使用函数就是调用此函数,并给函数传参,wapper表示装饰器函数,wapped表示被装饰函数,只要传入这两个参数即可
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import datetime
import time
import functools

def logger(fn):
def _logger(*args,**kwargs):
"""This is _logger function"""
print('Call _logger begin')
start = datetime.datetime.now()
ret = fn(*args,**kwargs)
delta = (datetime.datetime.now() - start).total_seconds()
print('Call _logger end')
print(delta)
functools.update_wrapper(_logger,fn) # 此时调用函数属性变更函数
return ret
return _logger

@logger # add = logger(add)
def add(x,y):
"""This is add function"""
print('#####################')
time.sleep(2)
return x + y

print(' return: {}\n name: {}\n doc: {}'.format(add(4,5),add.__name__,add.__doc__))
Call _logger begin
#####################
Call _logger end
2.000202
 return: 9
 name: add
 doc: This is add function

装饰器案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import datetime
import time
import functools

def looger(fn):
@functools.wraps(fn) # _logger = functools.wraps)(fn)(_logger) #调用装饰器进行函数属性变更
def _loogger(*args,**kwargs):
"""This is _logger function"""
print('Call function begin')
start = datetime.datetime.now()
ret = fn(*args,**kwargs)
delta = (datetime.datetime.now() - start).total_seconds()
print(delta)
return ret
return _logger

@logger # add = logger(add)
def add(x,y):
"""This is add function"""
time.sleep(1)
return x + y

print('return:{} \n name:{} \n doc:{}'.format(add(4,5),add.__name__,add.__doc__))
Call _logger begin
Call _logger end
1.000573
return:9 
 name:add 
 doc:This is add function
-------------本文结束感谢您的阅读-------------
0%